/*
 * GWorldLeagueImpl.java
 * Created on 2 nov. 2008
 *
 * Glacéo Internet Platform
 * http://sourceforge.net/projects/chm/
 *
 * Copyright (c) 2005-2009, Jens and Jan Janke (VirtualHockey Project)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hockeo.vhbase.data.impl.contest;


import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.hockeo.vhbase.conf.GRulesConstants;
import com.hockeo.vhbase.data.dao.club.GClub;
import com.hockeo.vhbase.data.dao.club.GClubWLHistoryRecord;
import com.hockeo.vhbase.data.dao.contest.GChampionsLeague;
import com.hockeo.vhbase.data.dao.contest.GContest;
import com.hockeo.vhbase.data.dao.contest.GContestElement;
import com.hockeo.vhbase.data.dao.contest.GContestRound;
import com.hockeo.vhbase.data.dao.contest.GKO3ContestElement;
import com.hockeo.vhbase.data.dao.contest.GRoundRobinContestElement;
import com.hockeo.vhbase.data.dao.season.GMatchday;
import com.hockeo.vhbase.data.dao.season.GMoveday;
import com.hockeo.vhbase.data.dao.season.GSeason;
import com.hockeo.vhbase.data.helper.GContestRoundType;
import com.hockeo.vhbase.data.helper.GContestUtils;
import com.hockeo.vhbase.data.helper.GIntCupParticipant;
import com.hockeo.vhbase.error.GDataException;
import com.hockeo.vhbase.error.GException;

/**
 * Standard implementation for the international contest "Champions League".
 *
 * @version $Id$
 * @author jjanke
 */
@Transactional( propagation = Propagation.MANDATORY, rollbackFor = { GException.class } )
public class GWorldLeagueImpl extends GIntCupImpl
{
  private static Log d_logger = LogFactory.getLog( GWorldLeagueImpl.class );

  public void createStructure( GSeason season, GContest contest ) throws GDataException
  {
    if ( contest.getNumTeams() != 48 )
      throw new IllegalStateException(
          "The World League Implementation currently expects exactly 48 first round participants." );

    // How many CL participants are relegated to the WL after the qualification?
    //
    GChampionsLeague cl = d_dao.findEntity( GChampionsLeague.class, GRulesConstants.SPECIAL_CONTEST_ID_CHAMPIONS_LEAGUE );
    int nNumCLRelegated = (int) d_dao.findNumChampionsLeagueParticipants( season ) - cl.getNumTeams();

    if ( nNumCLRelegated < 0 )
      nNumCLRelegated = 0;

    // World League structure
    //
    List<GIntCupParticipant> list = d_dao.findClubsForWorldLeague( season );
    GContestRound roundQR = null, roundFirst = null;
    int nNumRound = 1;
    int nNumDirectlyQualified = list.size() + nNumCLRelegated;

    // Qualification (standard KO round)
    //
    if ( nNumDirectlyQualified > contest.getNumTeams() )
    {
      roundQR = GContestUtils.createKORound( contest, nNumRound++, GContestRoundType.QUALIFICATION_1, 49,
          nNumDirectlyQualified - contest.getNumTeams(), false );

      nNumDirectlyQualified = contest.getNumTeams() - ( nNumDirectlyQualified - contest.getNumTeams() );
    }
    else if ( list.size() < contest.getNumTeams() )
    {
      d_logger.warn( "No World League will be configured because not enough participants have been determined " + "("
          + contest.getNumTeams() + " required, " + nNumDirectlyQualified + " determined)." );

      return;
    }

    // Main round (12 groups of 4 teams)
    //
    roundFirst = GContestUtils.createRoundRobinRound( contest, nNumRound++, GContestRoundType.ROUND_MAIN, 25, 12, 4, 2, 2, 0, 0, false );

    // Intermediate round (8 groups of 3 teams)
    //
    GContestUtils.createRoundRobinRound( contest, nNumRound++, GContestRoundType.ROUND_INTERMEDIATE, 9, 8, 3, 2, 1, 0, 0, false );

    // Quarter final
    //
    GContestUtils.createKORound( contest, nNumRound++, GContestRoundType.QUARTER_FINAL, 5, 4, false );

    // Semi final
    //
    GContestUtils.createKORound( contest, nNumRound++, GContestRoundType.SEMI_FINAL, 3, 2, false );

    // Final (single KO match)
    //
    GContestRound roundFinal = GContestUtils.createKORound( contest, nNumRound++, GContestRoundType.FINAL, 2, 1, false );
    roundFinal.setLastRound( true );

    // Persist the created rounds
    //
    int nNumLeftKOMatches = 0;
    int nNumLeftRRMatches = 0;
    for ( int i = contest.getContestRounds().size() - 1; i >= 0; i-- )
    {
      GContestRound round = contest.getContestRounds().get( i );
      GContestElement elementFirst = round.getContestElements().get( 0 );

      // Set the matches that remain to be played after this round
      //
      round.setRemainingKOMatchesSameGA( nNumLeftKOMatches );
      round.setRemainingRRMatchesSameGA( nNumLeftRRMatches );

      // Update the match variables after they have been set to the current round so they
      // are up-to-date for the next round
      //
      if ( elementFirst.instanceOf( GRoundRobinContestElement.class ) )
        nNumLeftRRMatches += elementFirst.getNumMatchesPerClub();
      else if ( elementFirst.instanceOf( GKO3ContestElement.class ) )
        nNumLeftKOMatches += 2;
      else
        throw new IllegalStateException( "Unsupported contest element type: "
            + elementFirst.getGEntityClass().getName() );

      d_dao.makePersistent( round );
    }

    // Assign clubs to main or first round
    //
    int i = 0;
    while ( i < nNumDirectlyQualified - nNumCLRelegated )
    {
      GIntCupParticipant part = list.get( i++ );
      GClub club = d_dao.findEntity( GClub.class, part.getClubId() );
      GClubWLHistoryRecord chr = new GClubWLHistoryRecord( season, club, roundFirst, part.getSeeding() );

      d_dao.makePersistent( chr );
    }

    while ( i < list.size() )
    {
      GIntCupParticipant part = list.get( i++ );
      GClub club = d_dao.findEntity( GClub.class, part.getClubId() );
      GClubWLHistoryRecord chr = new GClubWLHistoryRecord( season, club, roundQR, part.getSeeding() );

      d_dao.makePersistent( chr );
    }
  }

  public void assignMatchdaysToMovedays( GContest contest ) throws GDataException
  {
    final boolean fQualification = contest.getContestRounds().size() == 6;
    final int nNumRequiredMovedays = fQualification ? 9 : 8;
    List<GMoveday> listAvailableMovedays = d_dao.findReservedMovedays( contest );

    // Check if we have enough movedays and remove superflous rounds at the beginning of
    // the whole contest.
    //
    if ( listAvailableMovedays.size() < nNumRequiredMovedays )
      throw new GDataException( "There are not enough reserved movedays for the WL available.\nThe contest "
          + contest.getId() + " requires " + nNumRequiredMovedays + " movedays, but only "
          + listAvailableMovedays.size() + " are available." );
    else if ( listAvailableMovedays.size() > nNumRequiredMovedays )
    {
      int nToBeRemoved = listAvailableMovedays.size() - nNumRequiredMovedays;

      for ( int i = 0; i < nToBeRemoved; i++ )
        listAvailableMovedays.remove( 0 );
    }

    // Do the actual attachment of matchdays
    //
    int iStartIndex = 0;
    for ( GContestRound round : contest )
    {
      switch ( round.getRoundType() )
      {
        case QUALIFICATION_1:
        {
          for ( GContestElement element : round )
            for ( GMatchday matchday : d_dao.findMatchdays( element ) )
              listAvailableMovedays.get( iStartIndex ).addMatchday( matchday );

          iStartIndex++;
          break;
        }
        case ROUND_MAIN:
        {
          for ( GContestElement element : round )
            element.attachMatchdaysToMovedays( listAvailableMovedays.subList( iStartIndex, iStartIndex + 3 ),
                d_dao.findMatchdays( element ), true );

          iStartIndex += 3;
          break;
        }
        case ROUND_INTERMEDIATE:
        {
          for ( GContestElement element : round )
            element.attachMatchdaysToMovedays( listAvailableMovedays.subList( iStartIndex, iStartIndex + 2 ),
                d_dao.findMatchdays( element ), true );

          iStartIndex += 2;
          break;
        }
        case QUARTER_FINAL:
        case SEMI_FINAL:
        case FINAL:
        {
          // The '+ 1' operation is done to account for the fact that the main round uses
          // two movedays and therefore one more than all other rounds
          //
          for ( GContestElement element : round )
            for ( GMatchday matchday : d_dao.findMatchdays( element ) )
              listAvailableMovedays.get( iStartIndex ).addMatchday( matchday );

          iStartIndex++;
          break;
        }
        default:
          throw new IllegalStateException( "Unexpected WL round type: " + round.getRoundType().name() );
      }
    }
  }
}
